#!/usr/bin/python
# -*- coding: utf-8 -*-
# pglvmbackup.py
#
#########################################################################################
#
# $Id$
# $Author$
# $Date$
# $LastChangedBy$
# $LastChangedDate$
# $Revision$
#
#########################################################################################

# imports

import psycopg2
import sys
import os
import time
from time import *
from optparse import OptionParser 
import gc
import logging
import platform

# operational time

t1 = clock()

# enable gc

gc.enable()

# OptionParser

parser = OptionParser("pglvmbackup.py [options]")

# Options

parser.add_option("-u", "--dbuser", dest="dbuser", help="database user")
parser.add_option("-p", "--dbpassword", dest="dbpassword", help="database password")
parser.add_option("-c", "--compression", dest="compression", default="gzip", help="compression tool: gzip, pigz; default: gzip")
parser.add_option("-v", "--volumegroup", dest="volumegroup", help="volume group name")
parser.add_option("-l", "--logicalvolume", dest="logicalvolume", help="logical volume name")
parser.add_option("-m", "--mountpoint", dest="mountpoint", help="mointpoint for temporary snapshot")
parser.add_option("-s", "--size", dest="size", help="snapshot size")
parser.add_option("-n", "--name", dest="name", help="name of temporary snapshot LV")
parser.add_option("-f", "--filesystem", dest="filesystem", help="filesystem of logical volume")
parser.add_option("-t", "--target", dest="target", help="target (directory+filename) for backup file")
parser.add_option("-d", "--debug", dest="debug", default="false", help="useful for debugging; default: false")
parser.add_option("-o", "--port", dest="port", default="5432", help="database port; default: 5432")

# parse args

(options, args) = parser.parse_args()

# define logging

log = logging.getLogger("pglvmbackup")
out_hdlr = logging.StreamHandler(sys.stdout)
out_hdlr.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
if options.debug == 'true':
    out_hdlr.setLevel(logging.DEBUG)
else:
    out_hdlr.setLevel(logging.ERROR)
log.addHandler(out_hdlr)
log.setLevel(logging.DEBUG)

log.debug("starting pglvmbackup")

# log all options for debugging

log.debug(options)

# log some system information

log.debug(sys.platform)
log.debug(os.getuid())
log.debug(os.getpid())
log.debug(os.getcwd())
log.debug(os.uname())
log.debug(os.times())
log.debug(os.name)
log.debug(os.environ)
log.debug(platform.machine())
log.debug(platform.version())
log.debug(platform.platform())
log.debug(platform.uname())
log.debug(platform.system())
log.debug(platform.processor())

log.debug("check arguments")

# check arguments

if not options.dbuser:
    log.debug("ERROR: option dbuser not given")
    exit(1)
if not options.dbpassword:
    log.debug("ERROR: option dbpassword not given")
    exit(1)
if not options.volumegroup:
    log.debug("ERROR: option volumegroup not given")
    exit(1)
if not options.logicalvolume:
    log.debug("ERROR: option logicalvolume not given")
    exit(1)
if not options.mountpoint:
    log.debug("ERROR: option mountpoint not given")
    exit(1)
if not options.size:
    log.debug("ERROR: option size not given")
    exit(1)
if not options.name:
    log.debug("ERROR: option name not given")
    exit(1)
if not options.filesystem:
    log.debug("ERROR: option filesystem not given")
    exit(1)
if not options.target:
    log.debug("ERROR: option target not given")
    exit(1)

log.debug("check binaries")

# check binaries

if not os.path.isfile("/bin/gzip"):
    log.debug("/bin/gzip doesn't exist")
    exit(1)
if not os.path.isfile("/bin/mount"):
    log.debug("/bin/mount doesn't exist")
    exit(1)
if not os.path.isfile("/bin/umount"):
    log.debug("/bin/umount doesn't exist")
    exit(1)
if not os.path.isfile("/sbin/lvcreate"):
    log.debug("/bin/lvcreate doesn't exist")
    exit(1)
if not os.path.isfile("/sbin/lvremove"):
    log.debug("/bin/lvremove doesn't exist")
    exit(1)
if not os.path.isfile("/bin/tar"):
    log.debug("/bin/tar doesn't exist")
    exit(1)

log.debug("check if PostgreSQL WAL is active")

# check if PostgreSQL WAL is active, else error

log.debug("establishing PostgreSQL connection")

# establish PostgreSQL connection

try:
    conn = psycopg2.connect("dbname='postgres' user='"+options.dbuser+"' host='localhost' password='"+options.dbpassword+"' port="+options.port+" ")
    log.debug("PostgreSQL connection established")
except:
    log.debug("PostgreSQL connecction failed")
    exit(1)

log.debug("check database grants")

# check database grants

log.debug("define cursor")

# define cursor

cur = conn.cursor()

log.debug("start pg_backup")

# start pg_backup

cur.execute("""SELECT pg_start_backup('pglvmbackup', true);""")
rows = cur.fetchall()
for row in rows:
    log.debug(row[0])

log.debug("get PostgreSQL information")

# get PostgreSQL information

cur.execute("""SELECT version();""")
rows = cur.fetchall()
for row in rows:
    log.debug(row[0])

log.debug("create lvm snapshot")

# create lvm snapshot

foo = os.popen("lvcreate -s "+options.logicalvolume+" --name "+options.name+" --size "+options.size+" ")
log.debug(foo)
del foo
gc.collect()

# stop pg_backup

cur.execute("""SELECT pg_stop_backup();""")
rows = cur.fetchall()
for row in rows:
    log.debug(row[0])

log.debug("closing database connection")

# close database connection

conn.close()

log.debug("mount snapshot")

# mount snapshot

foo = os.popen("mount --verbose -o nouuid -t "+options.filesystem+" /dev/"+options.volumegroup+"/"+options.name+" "+options.mountpoint).read()
log.debug(foo)
del foo
gc.collect()

log.debug("zip it")

# gzip it

foo = os.popen("tar --verbose --use-compress-program="+options.compression+" -Ppcvf "+options.target+" --directory="+options.mountpoint+" .").read()
log.debug(foo)
del foo
gc.collect()

log.debug("umount snapshot")
# umount snapshot

foo = os.popen("umount --verbose "+options.mountpoint).read()
log.debug(foo)
del foo
gc.collect()

log.debug("remove lvm snapshot")
# remove lvm snapshot

foo = os.popen("lvremove --force /dev/"+options.volumegroup+"/"+options.name).read()
log.debug(foo)
del foo
gc.collect()

t2 = clock()
dt = t2 - t1
log.debug("time spent: "+str(dt))

log.debug("finished")
# finished

